package cn.maihe.elg.operation.centers.hymake.auth.utils;

import cn.hutool.crypto.asymmetric.SM2;
import cn.hutool.crypto.digest.MD5;
import cn.maihe.elg.operation.centers.hymake.auth.HyMakeAuthorization;
import cn.maihe.elg.operation.centers.hymake.config.HyMakeInfoConfig;
import cn.maihe.elg.operation.config.ElgAuthConfig;
import cn.maihe.elg.operation.utils.ElgLogger;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.binary.Base64;
import org.bouncycastle.asn1.*;
import org.bouncycastle.crypto.AsymmetricCipherKeyPair;
import org.bouncycastle.crypto.CipherParameters;
import org.bouncycastle.crypto.CryptoException;
import org.bouncycastle.crypto.InvalidCipherTextException;
import org.bouncycastle.crypto.engines.SM2Engine;
import org.bouncycastle.crypto.engines.SM2Engine.Mode;
import org.bouncycastle.crypto.params.*;
import org.bouncycastle.crypto.signers.SM2Signer;
import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPrivateKey;
import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPublicKey;
import org.bouncycastle.math.ec.ECPoint;
import org.bouncycastle.math.ec.custom.gm.SM2P256V1Curve;
import org.bouncycastle.pqc.legacy.math.linearalgebra.ByteUtils;

import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.math.BigInteger;
import java.security.*;
import java.security.spec.ECFieldFp;
import java.security.spec.EllipticCurve;


/**
 *  SM2工具类
 *  @author zengst
 *  @date 2021/8/3 12:01
 */
@Slf4j
public class SM2Util extends GMBaseUtil {
    //////////////////////////////////////////////////////////////////////////////////////
    /*
     * 以下为SM2推荐曲线参数
     */
    public static final SM2P256V1Curve CURVE = new SM2P256V1Curve();
    public final static BigInteger SM2_ECC_P = CURVE.getQ();
    public final static BigInteger SM2_ECC_A = CURVE.getA().toBigInteger();
    public final static BigInteger SM2_ECC_B = CURVE.getB().toBigInteger();
    public final static BigInteger SM2_ECC_N = CURVE.getOrder();
    public final static BigInteger SM2_ECC_H = CURVE.getCofactor();
    public final static BigInteger SM2_ECC_GX = new BigInteger(
            "32C4AE2C1F1981195F9904466A39C9948FE30BBFF2660BE1715A4589334C74C7", 16);
    public final static BigInteger SM2_ECC_GY = new BigInteger(
            "BC3736A2F4F6779C59BDCEE36B692153D0A9877CC62A474002DF32E52139F0A0", 16);
    public static final ECPoint G_POINT = CURVE.createPoint(SM2_ECC_GX, SM2_ECC_GY);
    public static final ECDomainParameters DOMAIN_PARAMS = new ECDomainParameters(CURVE, G_POINT,
            SM2_ECC_N, SM2_ECC_H);
    public static final int CURVE_LEN = BCECUtil.getCurveLength(DOMAIN_PARAMS);
    //////////////////////////////////////////////////////////////////////////////////////

    public static final EllipticCurve JDK_CURVE = new EllipticCurve(new ECFieldFp(SM2_ECC_P), SM2_ECC_A, SM2_ECC_B);
    public static final java.security.spec.ECPoint JDK_G_POINT = new java.security.spec.ECPoint(
            G_POINT.getAffineXCoord().toBigInteger(), G_POINT.getAffineYCoord().toBigInteger());
    public static final java.security.spec.ECParameterSpec JDK_EC_SPEC = new java.security.spec.ECParameterSpec(
            JDK_CURVE, JDK_G_POINT, SM2_ECC_N, SM2_ECC_H.intValue());

    //////////////////////////////////////////////////////////////////////////////////////

    public static final int SM3_DIGEST_LENGTH = 32;

    /**
     * 生成ECC密钥对
     *
     * @return ECC密钥对
     */
    public static AsymmetricCipherKeyPair generateKeyPairParameter() {
        SecureRandom random = new SecureRandom();
        return BCECUtil.generateKeyPairParameter(DOMAIN_PARAMS, random);
    }

    /**
     * 生成ECC密钥对
     *
     * @return
     * @throws NoSuchProviderException
     * @throws NoSuchAlgorithmException
     * @throws InvalidAlgorithmParameterException
     */
    public static KeyPair generateKeyPair() throws NoSuchProviderException, NoSuchAlgorithmException,
            InvalidAlgorithmParameterException {
        SecureRandom random = new SecureRandom();
        return BCECUtil.generateKeyPair(DOMAIN_PARAMS, random);
    }

    /**
     * 只获取私钥里的d值，32字节
     *
     * @param privateKey
     * @return
     */
    public static byte[] getRawPrivateKey(BCECPrivateKey privateKey) {
        return fixToCurveLengthBytes(privateKey.getD().toByteArray());
    }

    /**
     * 只获取公钥里的XY分量，64字节
     *
     * @param publicKey
     * @return 64字节数组
     */
    public static byte[] getRawPublicKey(BCECPublicKey publicKey) {
        byte[] src65 = publicKey.getQ().getEncoded(false);
        byte[] rawXY = new byte[CURVE_LEN * 2];//SM2的话这里应该是64字节
        System.arraycopy(src65, 1, rawXY, 0, rawXY.length);
        return rawXY;
    }

    /**
     * 加密-指定密文结构C1C3C2
     * @param pubHex 16进制公钥字符串
     * @param srcData 需要加密的字符串
     * @return 经过BASE64编码的字符串
     * @author zengst
     */
    public static String encrypt(String pubHex, String srcData) throws InvalidCipherTextException, UnsupportedEncodingException {
        ECPublicKeyParameters pubKey = BCECUtil.createECPublicKeyParameters(pubHex, CURVE, DOMAIN_PARAMS);
        byte[] encrypt = encrypt(pubKey, srcData.getBytes("UTF-8"));
        return Base64.encodeBase64String(encrypt);
    }

    /**
     * 解密-指定密文结构C1C3C2
     * @param priHex 16进制私钥字符串
     * @param srcData 经过BASE64编码的加密字符串
     * @return
     * @author zengst
     */
    public static String decrypt(String priHex, String srcData) throws InvalidCipherTextException, UnsupportedEncodingException {
        ECPrivateKeyParameters priKey = new ECPrivateKeyParameters(new BigInteger(ByteUtils.fromHexString(priHex)), DOMAIN_PARAMS);
        byte[] bytes = Base64.decodeBase64(srcData);
        byte[] decryptedData = decrypt(Mode.C1C3C2, priKey, bytes);
        return new String(decryptedData, "UTF-8");
    }

    /**
     * 加密-指定密文结构C1C2C3
     * @param pubHex 16进制公钥字符串
     * @param srcData 需要加密的字符串
     * @return 经过BASE64编码的字符串
     * @author zengst
     */
    public static String encryptC123(String pubHex, String srcData) throws InvalidCipherTextException, UnsupportedEncodingException {
        ECPublicKeyParameters pubKey = BCECUtil.createECPublicKeyParameters(pubHex, CURVE, DOMAIN_PARAMS);
        byte[] encrypt = encrypt(Mode.C1C2C3,pubKey, srcData.getBytes("UTF-8"));
        return Base64.encodeBase64String(encrypt);
    }

    /**
     * 解密-指定密文结构C1C2C3
     * @param priHex 16进制私钥字符串
     * @param srcData 经过BASE64编码的加密字符串
     * @return
     * @author zengst
     */
    public static String decryptC123(String priHex, String srcData) throws InvalidCipherTextException, UnsupportedEncodingException {
        ECPrivateKeyParameters priKey = new ECPrivateKeyParameters(new BigInteger(ByteUtils.fromHexString(priHex)), DOMAIN_PARAMS);
        byte[] bytes = Base64.decodeBase64(srcData);
        byte[] decryptedData = decrypt(Mode.C1C2C3, priKey, bytes);
        return new String(decryptedData, "UTF-8");
    }

    /**
     * @param pubKey  公钥
     * @param srcData 原文
     * @return 默认输出C1C3C2顺序的密文。C1为65字节第1字节为压缩标识，这里固定为0x04，后面64字节为xy分量各32字节。C3为32字节。C2长度与原文一致。
     * @throws InvalidCipherTextException
     */
    public static byte[] encrypt(BCECPublicKey pubKey, byte[] srcData) throws InvalidCipherTextException {
        ECPublicKeyParameters pubKeyParameters = BCECUtil.convertPublicKeyToParameters(pubKey);
        return encrypt(Mode.C1C3C2, pubKeyParameters, srcData);
    }

    /**
     * @param mode    指定密文结构，旧标准的为C1C2C3，新的[《SM2密码算法使用规范》 GM/T 0009-2012]标准为C1C3C2
     * @param pubKey  公钥
     * @param srcData 原文
     * @return 根据mode不同，输出的密文C1C2C3排列顺序不同。C1为65字节第1字节为压缩标识，这里固定为0x04，后面64字节为xy分量各32字节。C3为32字节。C2长度与原文一致。
     * @throws InvalidCipherTextException
     */
    public static byte[] encrypt(Mode mode, BCECPublicKey pubKey, byte[] srcData) throws InvalidCipherTextException {
        ECPublicKeyParameters pubKeyParameters = BCECUtil.convertPublicKeyToParameters(pubKey);
        return encrypt(mode, pubKeyParameters, srcData);
    }

    /**
     * @param pubKeyParameters 公钥
     * @param srcData          原文
     * @return 默认输出C1C3C2顺序的密文。C1为65字节第1字节为压缩标识，这里固定为0x04，后面64字节为xy分量各32字节。C3为32字节。C2长度与原文一致。
     * @throws InvalidCipherTextException
     */
    public static byte[] encrypt(ECPublicKeyParameters pubKeyParameters, byte[] srcData)
            throws InvalidCipherTextException {
        return encrypt(Mode.C1C3C2, pubKeyParameters, srcData);
    }

    /**
     * @param mode             指定密文结构，旧标准的为C1C2C3，新的[《SM2密码算法使用规范》 GM/T 0009-2012]标准为C1C3C2
     * @param pubKeyParameters 公钥
     * @param srcData          原文
     * @return 根据mode不同，输出的密文C1C2C3排列顺序不同。C1为65字节第1字节为压缩标识，这里固定为0x04，后面64字节为xy分量各32字节。C3为32字节。C2长度与原文一致。
     * @throws InvalidCipherTextException
     */
    public static byte[] encrypt(Mode mode, ECPublicKeyParameters pubKeyParameters, byte[] srcData)
            throws InvalidCipherTextException {
        SM2Engine engine = new SM2Engine(mode);
        ParametersWithRandom pwr = new ParametersWithRandom(pubKeyParameters, new SecureRandom());
        engine.init(true, pwr);
        return engine.processBlock(srcData, 0, srcData.length);
    }

    /**
     * @param priKey    私钥
     * @param sm2Cipher 默认输入C1C3C2顺序的密文。C1为65字节第1字节为压缩标识，这里固定为0x04，后面64字节为xy分量各32字节。C3为32字节。C2长度与原文一致。
     * @return 原文。SM2解密返回了数据则一定是原文，因为SM2自带校验，如果密文被篡改或者密钥对不上，都是会直接报异常的。
     * @throws InvalidCipherTextException
     */
    public static byte[] decrypt(BCECPrivateKey priKey, byte[] sm2Cipher) throws InvalidCipherTextException {
        ECPrivateKeyParameters priKeyParameters = BCECUtil.convertPrivateKeyToParameters(priKey);
        return decrypt(Mode.C1C2C3, priKeyParameters, sm2Cipher);
    }

    /**
     * @param mode      指定密文结构，旧标准的为C1C2C3，新的[《SM2密码算法使用规范》 GM/T 0009-2012]标准为C1C3C2
     * @param priKey    私钥
     * @param sm2Cipher 根据mode不同，需要输入的密文C1C2C3排列顺序不同。C1为65字节第1字节为压缩标识，这里固定为0x04，后面64字节为xy分量各32字节。C3为32字节。C2长度与原文一致。
     * @return 原文。SM2解密返回了数据则一定是原文，因为SM2自带校验，如果密文被篡改或者密钥对不上，都是会直接报异常的。
     * @throws InvalidCipherTextException
     */
    public static byte[] decrypt(Mode mode, BCECPrivateKey priKey, byte[] sm2Cipher) throws InvalidCipherTextException {
        ECPrivateKeyParameters priKeyParameters = BCECUtil.convertPrivateKeyToParameters(priKey);
        return decrypt(mode, priKeyParameters, sm2Cipher);
    }

    /**
     * @param priKeyParameters 私钥
     * @param sm2Cipher        默认输入C1C3C2顺序的密文。C1为65字节第1字节为压缩标识，这里固定为0x04，后面64字节为xy分量各32字节。C3为32字节。C2长度与原文一致。
     * @return 原文。SM2解密返回了数据则一定是原文，因为SM2自带校验，如果密文被篡改或者密钥对不上，都是会直接报异常的。
     * @throws InvalidCipherTextException
     */
    public static byte[] decrypt(ECPrivateKeyParameters priKeyParameters, byte[] sm2Cipher)
            throws InvalidCipherTextException {
        return decrypt(Mode.C1C3C2, priKeyParameters, sm2Cipher);
    }

    /**
     * @param mode             指定密文结构，旧标准的为C1C2C3，新的[《SM2密码算法使用规范》 GM/T 0009-2012]标准为C1C3C2
     * @param priKeyParameters 私钥
     * @param sm2Cipher        根据mode不同，需要输入的密文C1C2C3排列顺序不同。C1为65字节第1字节为压缩标识，这里固定为0x04，后面64字节为xy分量各32字节。C3为32字节。C2长度与原文一致。
     * @return 原文。SM2解密返回了数据则一定是原文，因为SM2自带校验，如果密文被篡改或者密钥对不上，都是会直接报异常的。
     * @throws InvalidCipherTextException
     */
    public static byte[] decrypt(Mode mode, ECPrivateKeyParameters priKeyParameters, byte[] sm2Cipher)
            throws InvalidCipherTextException {
        SM2Engine engine = new SM2Engine(mode);
        engine.init(false, priKeyParameters);
        return engine.processBlock(sm2Cipher, 0, sm2Cipher.length);
    }

    /**
     * 分解SM2密文
     *
     * @param cipherText 默认输入C1C3C2顺序的密文。C1为65字节第1字节为压缩标识，这里固定为0x04，后面64字节为xy分量各32字节。C3为32字节。C2长度与原文一致。
     * @return
     * @throws Exception
     */
    public static SM2Cipher parseSM2Cipher(byte[] cipherText) throws Exception {
        int curveLength = BCECUtil.getCurveLength(DOMAIN_PARAMS);
        return parseSM2Cipher(Mode.C1C3C2, curveLength, SM3_DIGEST_LENGTH, cipherText);
    }

    /**
     * 分解SM2密文
     *
     * @param mode       指定密文结构，旧标准的为C1C2C3，新的[《SM2密码算法使用规范》 GM/T 0009-2012]标准为C1C3C2
     * @param cipherText 根据mode不同，需要输入的密文C1C2C3排列顺序不同。C1为65字节第1字节为压缩标识，这里固定为0x04，后面64字节为xy分量各32字节。C3为32字节。C2长度与原文一致。
     * @return
     */
    public static SM2Cipher parseSM2Cipher(Mode mode, byte[] cipherText) throws Exception {
        int curveLength = BCECUtil.getCurveLength(DOMAIN_PARAMS);
        return parseSM2Cipher(mode, curveLength, SM3_DIGEST_LENGTH, cipherText);
    }

    /**
     * @param curveLength  曲线长度，SM2的话就是256位。
     * @param digestLength 摘要长度，如果是SM2的话因为默认使用SM3摘要，SM3摘要长度为32字节。
     * @param cipherText   默认输入C1C3C2顺序的密文。C1为65字节第1字节为压缩标识，这里固定为0x04，后面64字节为xy分量各32字节。C3为32字节。C2长度与原文一致。
     * @return
     * @throws Exception
     */
    public static SM2Cipher parseSM2Cipher(
            int curveLength, int digestLength, byte[] cipherText) throws Exception {
        return parseSM2Cipher(Mode.C1C3C2, curveLength, digestLength, cipherText);
    }

    /**
     * 分解SM2密文
     *
     * @param mode         指定密文结构，旧标准的为C1C2C3，新的[《SM2密码算法使用规范》 GM/T 0009-2012]标准为C1C3C2
     * @param curveLength  曲线长度，SM2的话就是256位。
     * @param digestLength 摘要长度，如果是SM2的话因为默认使用SM3摘要，SM3摘要长度为32字节。
     * @param cipherText   根据mode不同，需要输入的密文C1C2C3排列顺序不同。C1为65字节第1字节为压缩标识，这里固定为0x04，后面64字节为xy分量各32字节。C3为32字节。C2长度与原文一致。
     * @return
     */
    public static SM2Cipher parseSM2Cipher(Mode mode, int curveLength, int digestLength,
                                           byte[] cipherText) throws Exception {
        byte[] c1 = new byte[curveLength * 2 + 1];
        byte[] c2 = new byte[cipherText.length - c1.length - digestLength];
        byte[] c3 = new byte[digestLength];

        System.arraycopy(cipherText, 0, c1, 0, c1.length);
        if (mode == Mode.C1C2C3) {
            System.arraycopy(cipherText, c1.length, c2, 0, c2.length);
            System.arraycopy(cipherText, c1.length + c2.length, c3, 0, c3.length);
        } else if (mode == Mode.C1C3C2) {
            System.arraycopy(cipherText, c1.length, c3, 0, c3.length);
            System.arraycopy(cipherText, c1.length + c3.length, c2, 0, c2.length);
        } else {
            throw new Exception("Unsupported mode:" + mode);
        }

        SM2Cipher result = new SM2Cipher();
        result.setC1(c1);
        result.setC2(c2);
        result.setC3(c3);
        result.setCipherText(cipherText);
        return result;
    }

    /**
     * DER编码密文
     *
     * @param cipher 默认输入C1C3C2顺序的密文。C1为65字节第1字节为压缩标识，这里固定为0x04，后面64字节为xy分量各32字节。C3为32字节。C2长度与原文一致。
     * @return DER编码后的密文
     * @throws IOException
     */
    public static byte[] encodeSM2CipherToDER(byte[] cipher) throws Exception {
        int curveLength = BCECUtil.getCurveLength(DOMAIN_PARAMS);
        return encodeSM2CipherToDER(Mode.C1C3C2, curveLength, SM3_DIGEST_LENGTH, cipher);
    }

    /**
     * DER编码密文
     *
     * @param mode   指定密文结构，旧标准的为C1C2C3，新的[《SM2密码算法使用规范》 GM/T 0009-2012]标准为C1C3C2
     * @param cipher 根据mode不同，需要输入的密文C1C2C3排列顺序不同。C1为65字节第1字节为压缩标识，这里固定为0x04，后面64字节为xy分量各32字节。C3为32字节。C2长度与原文一致。
     * @return 按指定mode DER编码后的密文
     * @throws Exception
     */
    public static byte[] encodeSM2CipherToDER(Mode mode, byte[] cipher) throws Exception {
        int curveLength = BCECUtil.getCurveLength(DOMAIN_PARAMS);
        return encodeSM2CipherToDER(mode, curveLength, SM3_DIGEST_LENGTH, cipher);
    }

    /**
     * DER编码密文
     *
     * @param curveLength  曲线长度，SM2的话就是256位。
     * @param digestLength 摘要长度，如果是SM2的话因为默认使用SM3摘要，SM3摘要长度为32字节。
     * @param cipher       默认输入C1C3C2顺序的密文。C1为65字节第1字节为压缩标识，这里固定为0x04，后面64字节为xy分量各32字节。C3为32字节。C2长度与原文一致。
     * @return 默认输出按C1C3C2编码的结果
     * @throws IOException
     */
    public static byte[] encodeSM2CipherToDER(int curveLength, int digestLength, byte[] cipher)
            throws Exception {
        return encodeSM2CipherToDER(Mode.C1C3C2, curveLength, digestLength, cipher);
    }

    /**
     * @param mode         指定密文结构，旧标准的为C1C2C3，新的[《SM2密码算法使用规范》 GM/T 0009-2012]标准为C1C3C2
     * @param curveLength  曲线长度，SM2的话就是256位。
     * @param digestLength 摘要长度，如果是SM2的话因为默认使用SM3摘要，SM3摘要长度为32字节。
     * @param cipher       根据mode不同，需要输入的密文C1C2C3排列顺序不同。C1为65字节第1字节为压缩标识，这里固定为0x04，后面64字节为xy分量各32字节。C3为32字节。C2长度与原文一致。
     * @return 按指定mode DER编码后的密文
     * @throws Exception
     */
    public static byte[] encodeSM2CipherToDER(Mode mode, int curveLength, int digestLength, byte[] cipher)
            throws Exception {

        byte[] c1x = new byte[curveLength];
        byte[] c1y = new byte[curveLength];
        byte[] c2 = new byte[cipher.length - c1x.length - c1y.length - 1 - digestLength];
        byte[] c3 = new byte[digestLength];

        int startPos = 1;
        System.arraycopy(cipher, startPos, c1x, 0, c1x.length);
        startPos += c1x.length;
        System.arraycopy(cipher, startPos, c1y, 0, c1y.length);
        startPos += c1y.length;
        if (mode == Mode.C1C2C3) {
            System.arraycopy(cipher, startPos, c2, 0, c2.length);
            startPos += c2.length;
            System.arraycopy(cipher, startPos, c3, 0, c3.length);
        } else if (mode == Mode.C1C3C2) {
            System.arraycopy(cipher, startPos, c3, 0, c3.length);
            startPos += c3.length;
            System.arraycopy(cipher, startPos, c2, 0, c2.length);
        } else {
            throw new Exception("Unsupported mode:" + mode);
        }

        ASN1Encodable[] arr = new ASN1Encodable[4];
        arr[0] = new ASN1Integer(c1x);
        arr[1] = new ASN1Integer(c1y);
        if (mode == Mode.C1C2C3) {
            arr[2] = new DEROctetString(c2);
            arr[3] = new DEROctetString(c3);
        } else if (mode == Mode.C1C3C2) {
            arr[2] = new DEROctetString(c3);
            arr[3] = new DEROctetString(c2);
        }
        DERSequence ds = new DERSequence(arr);
        return ds.getEncoded(ASN1Encoding.DER);
    }

    /**
     * 解码DER密文
     *
     * @param derCipher 默认输入按C1C3C2顺序DER编码的密文
     * @return 输出按C1C3C2排列的字节数组，C1为65字节第1字节为压缩标识，这里固定为0x04，后面64字节为xy分量各32字节。C3为32字节。C2长度与原文一致。
     */
    public static byte[] decodeDERSM2Cipher(byte[] derCipher) throws Exception {
        return decodeDERSM2Cipher(Mode.C1C3C2, derCipher);
    }

    /**
     * @param mode      指定密文结构，旧标准的为C1C2C3，新的[《SM2密码算法使用规范》 GM/T 0009-2012]标准为C1C3C2
     * @param derCipher 根据mode输入C1C2C3或C1C3C2顺序DER编码后的密文
     * @return 根据mode不同，输出的密文C1C2C3排列顺序不同。C1为65字节第1字节为压缩标识，这里固定为0x04，后面64字节为xy分量各32字节。C3为32字节。C2长度与原文一致。
     * @throws Exception
     */
    public static byte[] decodeDERSM2Cipher(Mode mode, byte[] derCipher) throws Exception {
        ASN1Sequence as = DERSequence.getInstance(derCipher);
        byte[] c1x = ((ASN1Integer) as.getObjectAt(0)).getValue().toByteArray();
        byte[] c1y = ((ASN1Integer) as.getObjectAt(1)).getValue().toByteArray();
        byte[] c3;
        byte[] c2;
        if (mode == Mode.C1C2C3) {
            c2 = ((DEROctetString) as.getObjectAt(2)).getOctets();
            c3 = ((DEROctetString) as.getObjectAt(3)).getOctets();
        } else if (mode == Mode.C1C3C2) {
            c3 = ((DEROctetString) as.getObjectAt(2)).getOctets();
            c2 = ((DEROctetString) as.getObjectAt(3)).getOctets();
        } else {
            throw new Exception("Unsupported mode:" + mode);
        }

        int pos = 0;
        byte[] cipherText = new byte[1 + c1x.length + c1y.length + c2.length + c3.length];
        final byte uncompressedFlag = 0x04;
        cipherText[0] = uncompressedFlag;
        pos += 1;
        System.arraycopy(c1x, 0, cipherText, pos, c1x.length);
        pos += c1x.length;
        System.arraycopy(c1y, 0, cipherText, pos, c1y.length);
        pos += c1y.length;
        if (mode == Mode.C1C2C3) {
            System.arraycopy(c2, 0, cipherText, pos, c2.length);
            pos += c2.length;
            System.arraycopy(c3, 0, cipherText, pos, c3.length);
        } else if (mode == Mode.C1C3C2) {
            System.arraycopy(c3, 0, cipherText, pos, c3.length);
            pos += c3.length;
            System.arraycopy(c2, 0, cipherText, pos, c2.length);
        }
        return cipherText;
    }

    /**
     * 签名
     *
     * @param priKey  私钥
     * @param srcData 原文
     * @return DER编码后的签名值
     * @throws CryptoException
     */
    public static byte[] sign(BCECPrivateKey priKey, byte[] srcData) throws CryptoException {
        ECPrivateKeyParameters priKeyParameters = BCECUtil.convertPrivateKeyToParameters(priKey);
        return sign(priKeyParameters, null, srcData);
    }

    /**
     * 签名
     * 不指定withId，则默认withId为字节数组:"1234567812345678".getBytes()
     *
     * @param priKeyParameters 私钥
     * @param srcData          原文
     * @return DER编码后的签名值
     * @throws CryptoException
     */
    public static byte[] sign(ECPrivateKeyParameters priKeyParameters, byte[] srcData) throws CryptoException {
        return sign(priKeyParameters, null, srcData);
    }

    /**
     * 私钥签名
     *
     * @param priKey  私钥
     * @param withId  可以为null，若为null，则默认withId为字节数组:"1234567812345678".getBytes()
     * @param srcData 原文
     * @return DER编码后的签名值
     * @throws CryptoException
     */
    public static byte[] sign(BCECPrivateKey priKey, byte[] withId, byte[] srcData) throws CryptoException {
        ECPrivateKeyParameters priKeyParameters = BCECUtil.convertPrivateKeyToParameters(priKey);
        return sign(priKeyParameters, withId, srcData);
    }

    /**
     * 签名
     *
     * @param priKeyParameters 私钥
     * @param withId           可以为null，若为null，则默认withId为字节数组:"1234567812345678".getBytes()
     * @param srcData          源数据
     * @return DER编码后的签名值
     * @throws CryptoException
     */
    public static byte[] sign(ECPrivateKeyParameters priKeyParameters, byte[] withId, byte[] srcData)
            throws CryptoException {
        SM2Signer signer = new SM2Signer();
        CipherParameters param = null;
        ParametersWithRandom pwr = new ParametersWithRandom(priKeyParameters, new SecureRandom());
        if (withId != null) {
            param = new ParametersWithID(pwr, withId);
        } else {
            param = pwr;
        }
        signer.init(true, param);
        signer.update(srcData, 0, srcData.length);
        return signer.generateSignature();
    }

    /**
     * 将DER编码的SM2签名解码成64字节的纯R+S字节流
     *
     * @param derSign
     * @return 64字节数组，前32字节为R，后32字节为S
     */
    public static byte[] decodeDERSM2Sign(byte[] derSign) {
        ASN1Sequence as = DERSequence.getInstance(derSign);
        byte[] rBytes = ((ASN1Integer) as.getObjectAt(0)).getValue().toByteArray();
        byte[] sBytes = ((ASN1Integer) as.getObjectAt(1)).getValue().toByteArray();
        //由于大数的补0规则，所以可能会出现33个字节的情况，要修正回32个字节
        rBytes = fixToCurveLengthBytes(rBytes);
        sBytes = fixToCurveLengthBytes(sBytes);
        byte[] rawSign = new byte[rBytes.length + sBytes.length];
        System.arraycopy(rBytes, 0, rawSign, 0, rBytes.length);
        System.arraycopy(sBytes, 0, rawSign, rBytes.length, sBytes.length);
        return rawSign;
    }

    /**
     * 把64字节的纯R+S字节数组编码成DER编码
     *
     * @param rawSign 64字节数组形式的SM2签名值，前32字节为R，后32字节为S
     * @return DER编码后的SM2签名值
     * @throws IOException
     */
    public static byte[] encodeSM2SignToDER(byte[] rawSign) throws IOException {
        //要保证大数是正数
        BigInteger r = new BigInteger(1, extractBytes(rawSign, 0, 32));
        BigInteger s = new BigInteger(1, extractBytes(rawSign, 32, 32));
        ASN1EncodableVector v = new ASN1EncodableVector();
        v.add(new ASN1Integer(r));
        v.add(new ASN1Integer(s));
        return new DERSequence(v).getEncoded(ASN1Encoding.DER);
    }

    /**
     * 验签
     *
     * @param pubKey  公钥
     * @param srcData 原文
     * @param sign    DER编码的签名值
     * @return
     */
    public static boolean verify(BCECPublicKey pubKey, byte[] srcData, byte[] sign) {
        ECPublicKeyParameters pubKeyParameters = BCECUtil.convertPublicKeyToParameters(pubKey);
        return verify(pubKeyParameters, null, srcData, sign);
    }

    /**
     * 验签
     * 不指定withId，则默认withId为字节数组:"1234567812345678".getBytes()
     *
     * @param pubKeyParameters 公钥
     * @param srcData          原文
     * @param sign             DER编码的签名值
     * @return 验签成功返回true，失败返回false
     */
    public static boolean verify(ECPublicKeyParameters pubKeyParameters, byte[] srcData, byte[] sign) {
        return verify(pubKeyParameters, null, srcData, sign);
    }

    /**
     * 验签
     *
     * @param pubKey  公钥
     * @param withId  可以为null，若为null，则默认withId为字节数组:"1234567812345678".getBytes()
     * @param srcData 原文
     * @param sign    DER编码的签名值
     * @return
     */
    public static boolean verify(BCECPublicKey pubKey, byte[] withId, byte[] srcData, byte[] sign) {
        ECPublicKeyParameters pubKeyParameters = BCECUtil.convertPublicKeyToParameters(pubKey);
        return verify(pubKeyParameters, withId, srcData, sign);
    }

    /**
     * 验签
     *
     * @param pubKeyParameters 公钥
     * @param withId           可以为null，若为null，则默认withId为字节数组:"1234567812345678".getBytes()
     * @param srcData          原文
     * @param sign             DER编码的签名值
     * @return 验签成功返回true，失败返回false
     */
    public static boolean verify(ECPublicKeyParameters pubKeyParameters, byte[] withId, byte[] srcData, byte[] sign) {
        SM2Signer signer = new SM2Signer();
        CipherParameters param;
        if (withId != null) {
            param = new ParametersWithID(pubKeyParameters, withId);
        } else {
            param = pubKeyParameters;
        }
        signer.init(false, param);
        signer.update(srcData, 0, srcData.length);
        return signer.verifySignature(sign);
    }

    private static byte[] extractBytes(byte[] src, int offset, int length) {
        byte[] result = new byte[length];
        System.arraycopy(src, offset, result, 0, result.length);
        return result;
    }

    private static byte[] fixToCurveLengthBytes(byte[] src) {
        if (src.length == CURVE_LEN) {
            return src;
        }

        byte[] result = new byte[CURVE_LEN];
        if (src.length > CURVE_LEN) {
            System.arraycopy(src, src.length - result.length, result, 0, result.length);
        } else {
            System.arraycopy(src, 0, result, result.length - src.length, src.length);
        }
        return result;
    }

    public static void main(String[] args) throws UnsupportedEncodingException, InvalidCipherTextException {
//        String pubHex = "04AB557C05655219D95D25D3D49FB7E26058E93034392341D99BCEDAC0ABF0823EF5B08CD7BED0D8FECD50ACD65C1767B8EA6467F808132D9576381889C58A6684";
//        String priHex = "00B95AE4A5F81DBC603C9BF6C11380C971C391BD797938C53296016ACD687223F9";
//        String test = "BO3B//GFWOntGn9wmJ3dUCrQ/OKYFlZgSTy4U7ocJsiUxVhT1jxusgeqoyJKZ8mi9T/BfkHTQRj2ZD0m1gujQIBUawdSlWDQ3FAWpsBuG/F4S+YxxmzMyyUrZXZyhAwADaKr1m3D/dAaHySt46qZTuEK1dmyPU4ep5owHR/cBiEdLLifHyCYdj5/XJUbdxv3x9963tmLZfhUp4CIuf/jvyAMgA==";
//        String decrypt = decrypt(priHex, test);
//        System.out.println(decrypt);
//
//        String encrypt = encrypt(pubHex, decrypt);
//        System.out.println(encrypt);

        String jsonString = "{\"BIDDER\":\"{\\\"BIDDER_ADDRESS\\\":\\\"福建省漳州市南靖县山城镇人民广场1号 \\\",\\\"BIDDER_CONTACT_IDCARD\\\":\\\"350104198803134449\\\",\\\"BIDDER_CONTACT_IDCARD_TYPE\\\":\\\"01\\\",\\\"BIDDER_CONTACT_NAME\\\":\\\"刘平\\\",\\\"BIDDER_CONTACT_TEL\\\":\\\"15060008009\\\",\\\"BIDDER_CREDIT_CODE\\\":\\\"913506277242282547\\\",\\\"BIDDER_EAMIL\\\":\\\"454152231@qq.com\\\",\\\"BIDDER_NAME\\\":\\\"福建丰达建设发展有限公司\\\",\\\"LEGAL_PERSON_IDCARD\\\":\\\"350625198412120014\\\",\\\"LEGAL_PERSON_IDCARD_TYPE\\\":\\\"01\\\",\\\"LEGAL_PERSON_NAME\\\":\\\"黄景禧\\\",\\\"LEGAL_PERSON_TEL\\\":\\\"13615005316\\\",\\\"V_JSON\\\":\\\"{\\\\\\\"bidderCode\\\\\\\":\\\\\\\"913506277242282547\\\\\\\"}\\\"}\",\"TENDER_INFO\":{\"TENDERER_BANK_CODE\":\"33344\",\"TENDERER_CONTACT_ADDRESS\":\"福建省福州市鼓楼区\",\"VALID_PERIOD\":\"666\",\"TENDER_FILE_URL\":\"http://114.115.132.85/group1/M00/01/47/wKgApWVAmG2AMb5qAD0jk5KjXrw12.sspx\",\"INVITE_TYPE\":\"02\",\"PROJECT_DAYS\":\"按招标文件规定\",\"SECTION_CODE\":\"E3502110201001710002001\",\"TENDERER_BANK_ACCOUNT\":\"测试银行\",\"TENDERER_CONTACT_TEL\":\"15959155035\",\"TENDERER_BANK_OPENBANK\":\"建设银行\",\"SHARE\":\"1\",\"PROVINCE_CODE\":\"350000\",\"TENDER_NOTICE_URL\":\"http://120.41.36.88:9200/view?typeId=8&amp;busId=81f0f9c162cbb50128b0af1af8c442a9\",\"TENDERER_CONTACT\":\"李四\",\"CONTRACT_RECKON_PRICE\":\"10951800.000000\",\"TENDER_FILE_MD5\":\"D237CDB970E50579D68F663CD334DC71\",\"USE_PDFL\":\"0\",\"TOWN_CODE\":\"350101\",\"TENDER_PROJECT_CODE\":\"E3502110201001710002\",\"APPROVAL_CODE\":\"运营〔2023〕35号\",\"SECTION_NAME\":\"A7\",\"TENDER_PROJECT_TYPE\":\"A03\",\"CITY_CODE\":\"350100\",\"BID_BOND_MAX\":\"陆拾玖万元\",\"PROJECT_ADDRESS\":\"福建省福州市\",\"TENDERER\":\"测试招标人\",\"TENDERER_CREDIT_CODE\":\"99990000123456789A\",\"BID_BOND\":\"690000\",\"TENDER_PROJECT_NAME\":\"福州管理分公司辖区内高速公路2023至2026年度养护工程施工项目A1-A8标段\",\"BID_OPEN_TIME\":\"2026-10-31 09:00:00\",\"TENDER_NOTICE_ISSUE_TIME\":\"2023-10-31 14:05:53\"},\"APPLY_INFO\":\"{\\\"CREATE_TIME\\\":\\\"2025-04-07 11:25:37\\\",\\\"ORDER_NO\\\":\\\"TEST_DZBH250000252\\\"}\",\"GUARANTOR_NAME\":\"泉州丰泽得兴融资担保有限公司\",\"APPID\":\"A12345\",\"APPLY_ID\":\"1a29f769bbcf4846a3011deb1a43e059\",\"GUARANTOR_CREDIT_CODE\":\"91350503MA35A8601G\",\"TIMESTAMP\":\"20250407112537\",\"SECRET_KEY\":\"913506277242282547\",\"INVOICE\":\"{\\\"BANK_CODE\\\":\\\"0000018237002012\\\",\\\"BANK_OPENBANK\\\":\\\"泉州银行股份有限公司漳州龙文支行\\\",\\\"BIDDER_CODE\\\":\\\"913506277242282547\\\",\\\"COMPANY_ADDRESS\\\":\\\"福建省漳州市南靖县山城镇人民广场1号 \\\",\\\"COMPANY_EMAIL\\\":\\\"454152231@qq.com\\\",\\\"COMPANY_NAME\\\":\\\"福建丰达建设发展有限公司\\\",\\\"COMPANY_TEL\\\":\\\"454152231\\\",\\\"INVOICE_TYPE\\\":\\\"1\\\",\\\"RECEIVE_ADDRESS\\\":\\\"福建省漳州市南靖县山城镇人民广场1号 \\\",\\\"RECEIVE_NAME\\\":\\\"黄景禧\\\",\\\"RECEIVE_TEL\\\":\\\"13615005316\\\"}\",\"SIGNATURE\":\"CEB5D0D32D45B1D378EDD687845A710F\"}";
        String clearString = jsonString.replace("\\\"", "\"")
                .replace("\\\\\"","\"")
                .replace("\"[", "[")
                .replace("]\"", "]")
                .replace("\"{", "{")
                .replace("}\"", "}");
        System.out.println(clearString);


        System.out.println("test".contains("test") || "test".contains(null));

        String reqBody = "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";

        String pubHex = "04AB557C05655219D95D25D3D49FB7E26058E93034392341D99BCEDAC0ABF0823EF5B08CD7BED0D8FECD50ACD65C1767B8EA6467F808132D9576381889C58A6684";
        String priHex = "00B95AE4A5F81DBC603C9BF6C11380C971C391BD797938C53296016ACD687223F9";

        SM2 sm2 = new SM2(pubHex, priHex);

        String appId = "TBBH_ZT";
//        String appId = "TBBH_004";
        String slat = "MeTKJnC3fIAxeer8erADRTlJ";
        String requestId = "15ABE4FBE39A4CF5B63A38804AD5FAAC";
        String timestamp = "20220408141012";
        String sign = "9A2CD772D861585F6B375AFA2D4DDD8B";
        HyMakeInfoConfig.CenterInfo centerInfo = new HyMakeInfoConfig.CenterInfo();
        centerInfo.setAppId(appId);
        centerInfo.setVersion("WYS01");
        centerInfo.setSlat(slat);
        centerInfo.setSm2PriKey(priHex);
        centerInfo.setSm2PubKey(pubHex);


        ElgAuthConfig elgAuthConfig = new ElgAuthConfig();
        elgAuthConfig.setPriKey("123");
        elgAuthConfig.setPubKey("123");


        HyMakeAuthorization hyMakeAuthorization = new HyMakeAuthorization(elgAuthConfig,centerInfo);


        String decrypt = decrypt(priHex, reqBody);
        System.out.println("decrypt = " + decrypt);

        String base64S = Base64.encodeBase64String(decrypt.getBytes("utf-8"));

        System.out.println("base64S = " + base64S);

        boolean tf =   hyMakeAuthorization.verifySignature(requestId, decrypt, sign, timestamp);
        System.out.println("tf = " + tf);


        boolean tf1 = SM2Util.verifySignature(appId, slat, requestId, base64S, sign,timestamp);

        System.out.println("tf1 = " + tf1);


//                (version=null, appid=TBBH_ZT, sign=16458040C9B3294A116A9174A32F263F, requestid=509AAEC7997D4E11A3D722E2E4E7E6A1, timestamp=20220408101535)}
//

    }
    public static boolean verifySignature(String appId,String slat,String requestId, String reqEncryptBody, String signedMsg, String timestamp) {

        String messageDigest = buildMessageDigest(appId,slat,requestId, reqEncryptBody, timestamp);
        ElgLogger.debug(log, l -> l.debug("1签名摘要信息：{}", messageDigest));

        String signed = MD5.create().digestHex(messageDigest).toUpperCase();
        boolean verifyTF = signed.equalsIgnoreCase(signedMsg);

        return verifyTF;
    }

    static String buildMessageDigest(String appId,String slat,String requestId, String reqEncryptBody, String timestamp) {
        String messageDigest = String.format("%s%s%s%s%s", appId, requestId, slat, timestamp, reqEncryptBody);
        return messageDigest;
    }

}
